from datetime import datetime, date, timedelta
from typing import TypeVar, Generic, List, Sequence

from emi.bar.data import BarData, Interval
from emi.bar.db import BarDB, DayDB
from emi.config import ConfigItem
from emi.core.Context import Context
from emi.core.Runner import RunnerContext
from emi.core.task import Task
from emi.trade.data import TradeData
from emi.trade.trader import Portfolio, PortfolioData


class TraderStrategy:

    ALWAYS_TRADE = False  ## 是否使用简单交易方式
    INTERVAL = Interval.DAILY   ##  要求的行情频率
    CONFIG_ITEMS:List[ConfigItem] = []  ## 参数项目
    NAME = "未命名"  ## 策略名称


    """
    交易策略
    """


    def on_rest(self):
        pass

    def on_open(self,time:datetime,portofilio:Portfolio,interval:Interval):
        """
        行情市场开始
        :param time:
        :param market:
        :param portofilio:
        :return:
        """
        pass

    def on_close(self,time:datetime,portofilio:Portfolio,interval:Interval):
        """
        行情市场结束
        :param time:
        :param market:
        :param portofilio:
        :return:
        """
        pass

    def on_go(self,time:datetime,portofilio:Portfolio,interval:Interval):
        """
        行情市场进行中
        :param time:
        :param market:
        :param portofilio:
        :return:
        """
        pass

    def get_tick_at(self, time: datetime, symbol: str,interval:Interval) -> BarData:
        """
        返回市场行情的某一刻实时数据用于判断当前时刻是否产生交易。注意，不是实时快照数据，而是tick数据。
        interval = Interval.DAILY时：返回整天的BarData
        interval = Interval.M1时： 返回，time中的 hh_mm_ss的那一分钟数据。
        """
        raise NotImplementedError()

    def on_trade(self,time:datetime,portofilio:Portfolio,trades:List[TradeData]):
        """
        成功产生交易单。
        :param time:
        :param market:
        :param trades:
        :return:
        """
        pass



class TraderRunner:

    def __init__(self,**kwargs):
        """
        kwargs = {
            "init_capital": init_capital,  ##初始化资金
            "commission_rate": commission_rate,  ##成交交易费用
            "one_hand_size": one_hand_size,  ## 一手的数量
        }
        初始化数据。
        :param always_trade:  是否使用简单交易账户，总是能够交易。
        :param kwargs:
        :return:
        """
        self._KWARGS = kwargs


    def run_backtest(self,
                     context:Context,
                     day_list:List[date],
                     strategy:TraderStrategy,
                     interval:Interval = None,
                     )->Portfolio:

        """
        回测数据或者实时数据。
        :param market:   可以是
        :param strategy: 
        :param start: x
        :param end: 
        :return: 
        """
        kwargs = self._KWARGS
        portfoli_data = PortfolioData.of(**kwargs)
        init_capital = portfoli_data.params['init_capital']
        if interval is None:
            interval = strategy.__class__.INTERVAL

        from emi.trade.trader_engine import ITradeMarket

        strategy.on_rest()

        class _InterMarket(ITradeMarket):

            def __init__(self):
                self.time = None

            def do_open(self,time:datetime):
                self.time = time
                portfolio._makret_before_open(time)
                strategy.on_open(time,portfolio,interval)

            def do_close(self,time:datetime):
                self.time = time
                strategy.on_close(time, portfolio,interval)
                portfolio._market_after_close(time,self)

            def do_go(self,time:datetime):
                self.time = time
                strategy.on_go(time, portfolio,interval)
                portfolio._on_trade_time_go(time)

            def get_tick(self, symbol: str) -> BarData:
                return strategy.get_tick_at(self.time,symbol,interval)

            def get_now(self) -> datetime:
                return self.time

        market = _InterMarket()

        _ALWAYS_TRADE = strategy.__class__.ALWAYS_TRADE
        if _ALWAYS_TRADE:
            ##使用简单交易账户，总是能够交易。
            from emi.trade.trader_engine import SimpleExchangeEngine
            portfoli_data = PortfolioData.of(**kwargs)
            exechange_engine = SimpleExchangeEngine(portfoli_data, True)  ## 交易引擎
            portfolio = Portfolio(exechange_engine, init_capital)
        else:
            from emi.trade.trader_engine import ContextExchangeEngine
            exechange_engine = ContextExchangeEngine(context, portfoli_data,market,interval = strategy.INTERVAL)  ## 交易引擎
            portfolio = Portfolio(exechange_engine, init_capital)


        def on_trade_data_occur(time: datetime, trade_datas: List[TradeData]):
            strategy.on_trade(time,portfolio,trade_datas)

        portfolio.set_default_bar_realtime(market)
        portfolio.exechange_engine.set_trade_order_happen_callback(on_trade_data_occur) ## 注册成功交易单回调函数

        total_size = len(day_list)

        for index,day in enumerate(day_list):

            if isinstance(context,RunnerContext):
                context.set_progress(index / total_size)
                context.check_stop()

            time = datetime(day.year,day.month,day.day,hour=9,minute=0,second=0)
            market.do_open(time)


            OVER_TIME = datetime(day.year, day.month, day.day, hour=15, minute=0, second=59)

            if interval == Interval.DAILY:
                time = OVER_TIME
                market.do_go(time)

            else:
                if interval == Interval.M1:
                    minute_interval = 1
                elif interval == Interval.M5:
                    minute_interval = 5
                elif interval == Interval.M15:
                    minute_interval = 15
                elif interval == Interval.M30:
                    minute_interval = 30
                elif interval == Interval.H1:
                    minute_interval = 60
                else:
                    raise NotImplementedError(f"不支持的interval:{interval}")
                time = datetime(day.year, day.month, day.day, hour=9, minute=30, second=0)
                AM_TIME = datetime(day.year, day.month, day.day, hour=11, minute=30, second=59)
                while time <= AM_TIME:
                    market.do_go(time)
                    time = market.time + timedelta(minutes=minute_interval)
                market.time = datetime(day.year, day.month, day.day, hour=13, minute=0, second=0)
                while time <= OVER_TIME:
                    market.do_go(time)
                    time = market.time + timedelta(minutes=minute_interval)

            time = OVER_TIME
            market.do_close(time)

        return portfolio















